/*
This file is part of La Linea.

La Linea is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

La Linea is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with La Linea.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "FrameStencil.h"

using namespace std;

FrameStencil* FrameStencil::_currentTess = NULL;


FrameStencil::FrameStencil(void) : _displayList(-1)
{
}

FrameStencil::~FrameStencil(void)
{
	StencilNode* lastNode = 0;

	if(_curves.size() > 0)
		lastNode = _curves[_curves.size()-1]->_origins[1];

	deque<StencilCurve*>::iterator it = _curves.begin();
	for(;it!=_curves.end();++it) delete (*it);
	_curves.clear();

	if(lastNode)
		delete lastNode;
		
	if(_displayList != -1) glDeleteLists(_displayList, 1);
}

std::deque<StencilCurve*>& FrameStencil::getCurves(){
	return _curves;
}

void FrameStencil::addCurve(StencilCurve* c){
	assert (c!=NULL);
	if(_curves.size() != 0){
		if(c->_origins[1] == _curves[0]->_origins[0]){
			_curves.push_front(c);
		}
		else{
			_curves.push_back(c);
		}
	}
	else{
		_curves.push_back(c);
	}
}

void FrameStencil::tesselateContour(){
	_currentTess = this;

	std::vector<Vector2f> contour;
	std::deque<StencilCurve*>::iterator it, end;
	GLUtesselator *tesselator;
	
	if(_displayList != -1) glDeleteLists(_displayList, 1);
	
	tesselator = gluNewTess();
	assert (tesselator!=NULL);
	
	gluTessProperty(tesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
	
	gluTessCallback(tesselator, GLU_TESS_BEGIN, (void (CALLBACK *)())FrameStencil::stencilTessBegin);
	gluTessCallback(tesselator, GLU_TESS_END, (void (CALLBACK *)())FrameStencil::stencilTessEnd);
	gluTessCallback(tesselator, GLU_TESS_VERTEX, (void (CALLBACK *)())FrameStencil::stencilTessVertex);
	gluTessCallback(tesselator, GLU_TESS_ERROR, (void (CALLBACK *)())FrameStencil::stencilTessError);
	gluTessCallback(tesselator, GLU_TESS_COMBINE, (void (CALLBACK *)())FrameStencil::stencilTessCombine);
	
	// Compute the outline	
	for(it = _curves.begin(), end = _curves.end(); it != end; ++it){
		GLfloat length = (*it)->getLength();
		int currentQuality = (int)(length*10*quality);
		if(currentQuality <= 0) currentQuality = 3;

		GLfloat a = 1.0;
		GLfloat b = 0.0;
		
		for(unsigned int i = 0; i <= (unsigned int)currentQuality; i++){
			GLfloat X = (GLfloat)(*it)->_origins[0]->_pos.x *(a*a) + (*it)->_controlpoint->_pos.x*2*a*b + (*it)->_origins[1]->_pos.x*(b*b);
			GLfloat Y = (GLfloat)(*it)->_origins[0]->_pos.y *(a*a) + (*it)->_controlpoint->_pos.y*2*a*b + (*it)->_origins[1]->_pos.y*(b*b);
			contour.push_back(Vector2f(X,Y));

			a -= (GLfloat)1.0/currentQuality;
			b = (GLfloat)1.0 - a;
		}
	}
	
	_displayList = glGenLists(1);			// Create the display list
	if(!_displayList){
		_displayList = -1;
		return;
	}
	
	glNewList(_displayList, GL_COMPILE);	// Start compiling it
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	
	gluTessBeginPolygon(tesselator, 0);		// New polygon
	gluTessBeginContour(tesselator);		// Contour begin
	GLdouble* coords = new GLdouble[3*contour.size()];
	for (unsigned int i = 0; i < contour.size(); i++){
		coords[i*3+0] = contour[i].x;
		coords[i*3+1] = contour[i].y;
		coords[i*3+2] = 0.0;
		
		gluTessVertex(tesselator, &coords[i*3], &coords[i*3]);
	}
	
	gluTessEndContour(tesselator);			// Contour end
	gluTessEndPolygon(tesselator);			// Polygon end
	
	glEndList();							// End of the display list
	
	gluDeleteTess(tesselator);

	vector<GLdouble*>::iterator itV = _tempVertex.begin();
	for(; itV != _tempVertex.end(); itV++){
		if((*itV)) free(*itV);
	}
	_tempVertex.clear();
	FrameStencil::_currentTess = 0;

	delete[] coords;
}

int FrameStencil::getDisplayList(){
	if(_displayList == -1) tesselateContour();
	return _displayList;
}

void FrameStencil::invalidate(){
	if(_displayList != -1) glDeleteLists(_displayList,1);
	_displayList = -1;
}

// Glu callbacks
void CALLBACK FrameStencil::stencilTessBegin(GLenum drawMode)
{
    glBegin(drawMode);
}

void CALLBACK FrameStencil::stencilTessEnd()
{
    glEnd();
}

void CALLBACK FrameStencil::stencilTessVertex(const GLvoid *data)
{
    const GLdouble *ptr = (const GLdouble*)data;
    glVertex3dv(ptr);
}

void CALLBACK FrameStencil::stencilTessError(GLenum errorCode)
{
   const GLubyte *estring;

   estring = gluErrorString(errorCode);
   cout << "tessError : " << estring << endl;
}

void CALLBACK FrameStencil::stencilTessCombine(GLdouble coords[3], GLdouble *vertex_data[4], GLfloat weight[4], GLdouble **dataOut)
{
   GLdouble *vertex;
   vertex = (GLdouble *) malloc(3 * sizeof(GLdouble));
   assert (vertex!=NULL);
   vertex[0] = coords[0];
   vertex[1] = coords[1];
   vertex[2] = coords[2];
   *dataOut = vertex;

   FrameStencil::_currentTess->_tempVertex.push_back(vertex);
}